import { Provide, Inject, Config } from '@midwayjs/core';
import { HttpService } from '@midwayjs/axios';
import * as crypto from 'crypto';
import { Builder, parseStringPromise } from 'xml2js';

interface ICachedToken {
  token: string;
  expires_in: number;
  createdAt: number;
}

@Provide()
export class WechatService {
  @Inject()
  httpService: HttpService;

  @Config('wechat')
  wechatConfig: {
    appid: string;
    appsecret: string;
    redirectHost: string;
    mchId: string;
    apiKey: string; // 注意：这里可能是 apiKey 或 apiKeyV3，取决于你的支付版本
  };

  private accessTokenCache: ICachedToken;
  private jsapiTicketCache: ICachedToken;

  async getGlobalAccessToken() {
    if (
      this.accessTokenCache &&
      Date.now() - this.accessTokenCache.createdAt <
        (this.accessTokenCache.expires_in - 60) * 1000
    ) {
      return this.accessTokenCache.token;
    }

    const { appid, appsecret } = this.wechatConfig;
    const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}&secret=${appsecret}`;
    const result = await this.httpService.get(url);

    if (result.data.errcode) {
      throw new Error(`获取 global access_token 失败: ${result.data.errmsg}`);
    }

    this.accessTokenCache = { ...result.data, createdAt: Date.now() };
    return result.data.access_token;
  }

  async getJsApiTicket() {
    if (
      this.jsapiTicketCache &&
      Date.now() - this.jsapiTicketCache.createdAt <
        (this.jsapiTicketCache.expires_in - 60) * 1000
    ) {
      return this.jsapiTicketCache.token;
    }

    const accessToken = await this.getGlobalAccessToken();
    const url = `https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=${accessToken}&type=jsapi`;
    const result = await this.httpService.get(url);

    if (result.data.errcode !== 0) {
      throw new Error(`获取 jsapi_ticket 失败: ${result.data.errmsg}`);
    }

    this.jsapiTicketCache = {
      token: result.data.ticket,
      expires_in: result.data.expires_in,
      createdAt: Date.now(),
    };
    return result.data.ticket;
  }

  async createJsSdkSignature(url: string) {
    const jsapi_ticket = await this.getJsApiTicket();
    const nonceStr = Math.random().toString(36).substring(2, 15);
    const timestamp = Math.floor(Date.now() / 1000).toString();

    const signatureStr = `jsapi_ticket=${jsapi_ticket}&noncestr=${nonceStr}&timestamp=${timestamp}&url=${url}`;
    const signature = crypto
      .createHash('sha1')
      .update(signatureStr)
      .digest('hex');

    return {
      appId: this.wechatConfig.appid,
      timestamp,
      nonceStr,
      signature,
    };
  }

  async getAccessToken(code: string) {
    const { appid, appsecret } = this.wechatConfig;
    const url = `https://api.weixin.qq.com/sns/oauth2/access_token?appid=${appid}&secret=${appsecret}&code=${code}&grant_type=authorization_code`;

    const result = await this.httpService.get(url);
    if (result.data.errcode) {
      throw new Error(result.data.errmsg);
    }
    return result.data;
  }

  async getUserInfo(accessToken: string, openid: string) {
    const url = `https://api.weixin.qq.com/sns/userinfo?access_token=${accessToken}&openid=${openid}&lang=zh_CN`;
    const result = await this.httpService.get(url);
    if (result.data.errcode) {
      throw new Error(result.data.errmsg);
    }
    return result.data;
  }

  async createUnifiedOrder(
    openid: string,
    totalFee: number,
    body: string,
    tradeNo: string
  ) {
    const { appid, mchId } = this.wechatConfig;
    const nonce_str = Math.random().toString(36).substring(2, 15);

    const params = {
      appid,
      mch_id: mchId,
      nonce_str,
      body,
      out_trade_no: tradeNo,
      total_fee: totalFee,
      spbill_create_ip: '127.0.0.1',
      notify_url: 'http://your-domain.com/api/wechat/payment-notify',
      trade_type: 'JSAPI',
      openid,
    };

    const sign = this.createSign(params);
    params['sign'] = sign;

    const builder = new Builder({
      rootName: 'xml',
      cdata: true,
      headless: true,
    });
    const xmlData = builder.buildObject(params);

    const result = await this.httpService.post(
      'https://api.mch.weixin.qq.com/pay/unifiedorder',
      xmlData,
      { headers: { 'Content-Type': 'text/xml' } }
    );
    const parsedResult = await parseStringPromise(result.data, {
      explicitArray: false,
      explicitRoot: false,
    });

    if (
      parsedResult.return_code !== 'SUCCESS' ||
      parsedResult.result_code !== 'SUCCESS'
    ) {
      throw new Error(
        `统一下单失败: ${parsedResult.return_msg} | ${parsedResult.err_code_des}`
      );
    }

    const paySignParams = {
      appId: appid,
      timeStamp: Math.floor(Date.now() / 1000).toString(),
      nonceStr: nonce_str,
      package: `prepay_id=${parsedResult.prepay_id}`,
      signType: 'MD5',
    };

    const paySign = this.createSign(paySignParams, 'MD5');
    return { ...paySignParams, paySign };
  }

  private createSign(params: object, signType = 'MD5') {
    const { apiKey } = this.wechatConfig;
    const str =
      Object.keys(params)
        .filter(
          key =>
            params[key] !== undefined && params[key] !== '' && key !== 'sign'
        )
        .sort()
        .map(key => `${key}=${params[key]}`)
        .join('&') + `&key=${apiKey}`;

    if (signType === 'MD5') {
      return crypto.createHash('md5').update(str).digest('hex').toUpperCase();
    } else {
      return crypto
        .createHmac('sha256', apiKey)
        .update(str)
        .digest('hex')
        .toUpperCase();
    }
  }
}
